package nl.cypherpunk.proLearner;

import de.learnlib.api.SUL;
import de.learnlib.api.logging.LearnLogger;
import de.learnlib.api.oracle.MembershipOracle;
import de.learnlib.api.query.DefaultQuery;
import de.learnlib.api.query.Query;
import net.automatalib.words.Word;
import net.automatalib.words.WordBuilder;

import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;

@ParametersAreNonnullByDefault
public class ModifiedLogOracle<I, D> implements MembershipOracle.MealyMembershipOracle<I,D> {
    public static class MealyLogOracle<I,O> extends ModifiedLogOracle<I,O> {
        public MealyLogOracle(SUL<I, O> sul, LearnLogger logger) {
            super(sul, logger);
        }
    }

    LearnLogger logger;
    SUL<I, D> sul;
    List<Word<I>> closedQuery;
    ArrayList<Word<D>> closedAnwser;
    ArrayList<Word<I>> logQuery;
    ArrayList<Word<D>> logAnwser;
    Boolean flag;
    public ModifiedLogOracle(SUL<I,D> sul, LearnLogger logger) {
        this.sul = sul;
        this.logger = logger;
        this.closedQuery = new ArrayList<>();
        this.closedAnwser = new ArrayList<>();
        this.logQuery = new ArrayList<>();
        this.logAnwser = new ArrayList<>();
        flag = false;
    }
    // delete the subtype
    public void updateLog(I subtype)
    {
        flag = true;

        for(int i = 0;i<logQuery.size() ;)
        {
            Word<I> query = logQuery.get(i);
            if(query.toString().contains(subtype.toString()))
            {
                logQuery.remove(query);
                logAnwser.remove(i);
            }
            else
                i++;
        }
    }

    @Override
    public Word<D> answerQuery(Word<I> prefix, Word<I> suffix) {
        int i = 0;
        Word<I> all = prefix.concat(suffix);
        if(flag) {
            for (Word<I> query : logQuery) {
                if (query.toString().equals(all.toString())) {
                    Word<D> tmp = logAnwser.get(i);
                    System.out.println("[" + prefix.toString() + " | " + suffix.toString() + " / " + tmp.subWord(0, prefix.length()).toString() + " | " + tmp.subWord(prefix.length(), all.length()).toString() + "]");
                    return tmp.subWord(prefix.length(), all.length());
                }
                i++;
            }
        }
        i = 0;
        for (Word<I> q : closedQuery)
        {
            if(q.isPrefixOf(prefix))
            {
                WordBuilder<D> wbSuffix = new WordBuilder<>(suffix.length());
                for(int j = 0; j < suffix.length(); j++){
                    wbSuffix.add((D)"ConnectionClosed");
                }
                System.out.println("[" + prefix.toString() + " | " + suffix.toString() +  " / " + closedAnwser.get(i).toString() + " | " + wbSuffix.toWord().toString() + "]");
                this.logQuery.add(prefix.concat(suffix));
                this.logAnwser.add(closedAnwser.get(i).concat(wbSuffix.toWord()));
                return wbSuffix.toWord();
            }
            else if(q.isPrefixOf(all)) {
                WordBuilder<D> wbSuffix = new WordBuilder<>(suffix.length());
                wbSuffix.append(closedAnwser.get(i).subWord(prefix.length(),q.length()));
                for(int j = q.length(); j < all.length(); j++){
                    wbSuffix.add((D)"ConnectionClosed");
                }
                System.out.println("[" + prefix.toString() + " | " + suffix.toString() +  " / " + closedAnwser.get(i).subWord(0, prefix.length()).toString() + " | " + wbSuffix.toWord().toString() + "]");
                this.logQuery.add(prefix.concat(suffix));
                this.logAnwser.add(closedAnwser.get(i).subWord(0, prefix.length()).concat(wbSuffix.toWord()));
                return wbSuffix.toWord();
            }
            i++;
        }
        this.sul.pre();
        try {
            // Prefix: Execute symbols, only log output
            WordBuilder<D> wbPrefix = new WordBuilder<>(prefix.length());
            for(I sym : prefix) {
                D tmp = this.sul.step(sym);
                wbPrefix.add(tmp);
                i++;
                if(tmp.toString().contains("Closed"))
                {
                    closedQuery.add(prefix.prefix(i));
                    closedAnwser.add(wbPrefix.toWord());
                    break;
                }
            }
            for(;i<prefix.length();i++)
            {
                wbPrefix.add((D)"ConnectionClosed");
            }

            // Suffix: Execute symbols, outputs constitute output word
            WordBuilder<D> wbSuffix = new WordBuilder<>(suffix.length());
            i = 0;
            for(I sym : suffix) {
                D tmp = this.sul.step(sym);
                wbSuffix.add(tmp);
                i++;
                if(tmp.toString().contains("Closed"))
                {
                    closedQuery.add(prefix.concat(suffix.prefix(i)));
                    closedAnwser.add(wbPrefix.toWord().concat(wbSuffix.toWord()));
                    break;
                }
            }
            for(;i<suffix.length();i++)
            {
                wbSuffix.add((D)"ConnectionClosed");
            }
            System.out.println("[" + prefix.toString() + " | " + suffix.toString() +  " / " + wbPrefix.toWord().toString() + " | " + wbSuffix.toWord().toString() + "]");
            this.logQuery.add(prefix.concat(suffix));
            this.logAnwser.add(wbPrefix.toWord().concat(wbSuffix.toWord()));
            return wbSuffix.toWord();
        }
        finally {
            sul.post();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Word<D> answerQuery(Word<I> query) {
        return answerQuery((Word<I>)Word.epsilon(), query);
    }

    @Override
    public MembershipOracle<I, Word<D>> asOracle() {
        return this;
    }

    @Override
    public void processQueries(Collection<? extends Query<I, Word<D>>> queries) {
        for (Query<I,Word<D>> q : queries) {
            Word<D> output = answerQuery(q.getPrefix(), q.getSuffix());
            q.answer(output);
        }
    }

}
